React - Passing State to child component - reactjs

My Parent Class which holds the overall App state
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: 1,
};
this.handleChange = this.handleChange.bind(this);
};
handleChange(event) {
const target = event.target;
const newValue = target.value;
if( Math.sign(newValue) === 1 ) {
this.setState({
inputValue: newValue
});
}
}
render() {
return (
<EngInput type="number" value={this.state.inputValue} onChange={this.handleChange}/>
);
}
}
My child component to which I am passing the state as props.
class EngInput extends React.Component {
render() {
return (
<input
type="number"
defaultValue={this.props.value}
onChange={this.props.onChange}
/>
);
}
}
The logic I am trying to implement being - The child input component should accept only positive numbers. If its a negative number, the state should not change and the UI should update to the inputValue instead of the newValue.
But, whats happening in the above code is, even though the state does not change to non negative value, the UI still accepts negative value.
How to implement a logic like, if the value is negative, the UI should still show the old positive value from state.

Use value instead of defaultValue to control the input from the parent component.
class EngInput extends React.Component {
render() {
return (
<input
type="number"
value={this.props.value}
onChange={this.props.onChange}
/>
);
}
}

Related

How do I make react input show state value-ReactJS

By using class, here is a small ref for all of you :
Class ShowValue Extends Component{
constructor(props){
super(props);
this.state = {
name: “ “,
}
}
onChangeHandler = (e) => {
this.setState(
name: e.target.value,
)
}
render(){
return (
<input type=” text” onChange={this.onChangeHandler} value=” My name”>
<p>{this.state.name}</p>
);
}
}
Am I able to create a variable for each input field, and listening on the onChange event and then call the “set” function for that variable by using hooks.
I have changed your code, now if you write in the input text it will be saved in your state name or vice-versa if you write in your code something in state name it will appear in the input.
I recommend you to check some tutorials or even the official doc
import React from 'react';
class ShowValue extends React.Component {
constructor(props) {
super(props);
this.state = {
name: ''
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ name: event.target.value });
}
render() {
return (
<div>
<input
value={this.state.name}
type="text"
onChange={this.handleChange}
/>
<p>{this.state.name}</p>
</div>
);
}
}
export default ShowValue;
Have in mind:
You need to write a parent element to return different child elements otherwise it will give you error. So i've wrapped the input and p element.
You can not use ” for strings in javascript single quotes or double quotes
' or "
Demo: https://stackblitz.com/edit/react-j4a4dv?file=src%2FShowValue.js
Show the value inside the input by setting the value property of input
<input value={this.state.name} type=”text” onChange={this.onChangeHandler} >

Moving props data to state to generate forms

I'm planning to add a prefilled form with React. I have the actual data on props. This is what I came up with.
#connect(...)
class Some extends React.Component {
state = {
...this.props.auth.user
}
render() {
// Create a form using the data on state
}
}
It looks not correct since I'm not using a react lifecycle hook here. I would like to ask if there is a better practice to achieve what I'm trying to do.
I am not sure about your architecture,since you are using uncontrolled component here, it is recommended to keep the source of truth at one place.
you can do something like this:
#connect(...)
class Some extends React.Component {
constructor(props) {
super(props);
this.state = {
userName:this.props.auth.user
}
}
handleChange = (event) => {
this.setState({userName: event.target.value});
}
render() {
return(
<div>
<input onChange={this.handleChange} id="some" type="text" value= {this.state.userName}/>
</div>
)
}
}
If you want to use controlled component that is controlled through parent/container. you can manage the values through props and set the props onChange.
So to elaborate on my previous responses you would do something like this to achieve what you want:
#connect(...)
class Some extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
}
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
render() {
return(
<div>
<input onChange={this.handleChange} id="some" type="text" value= {this.state.value|| this.props.value}/>
</div>
)
}
}
While your value is an empty string (in the state), the fields will be populated from your props and as soon as you start typing it will overwrite the prepopulated values with the ones in your state.
Best practices would be to actually have a Component that handles this logic and then passes the props to the form that should be just a dumb presentational component:
class SomeController extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
}
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
return (<Form handleChange={this.handleChange} value={this.state.value} />)
}
And then your form component:
const Form = (props) => (
<form>
<input onChange={props.handleChange} value={props.value} />
</form>
);
Hope this explanation helps.

Pass state as props from parent to child and then update parent from child onSubmit

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>

React child receive old state props, NOT updated state props

I have a funky set up. I need a multi stage registration form. I have a parent:
class ContactPage extends React.Component {
constructor(props){
super(props);
this.state = {
stage:0,
name:'',
message:'',
email:'',
phone:''
}
this.setName=(e)=>{
this.setState({name:e});
}
this.setMessage=(e)=>{
this.setState({message:e});
}
this.setEmail=(e)=>{
this.setState({email:e});
}
this.setPhone=(e)=>{
this.setState({phone:e});
}
this.nextStage=()=>{
if(this.state.stage < 3){
this.setState({stage:this.state.stage+1})
}
}
this.previousStage=()=>{
if(this.state.stage >= 1){
this.setState({stage:this.state.stage-1})
}
}
this.stage = [
<ContactName onChange={this.setName} />,
<ContactInfo />,
<ContactMessage name={this.state.name} onChange={this.setMessage} />,
<Send />
]
}
render(){
return (
<div>
{this.stage[this.state.stage]}
<button primary style={style.button} onClick={this.previousStage}> Previous </button>
<button primary style={style.button} onClick={this.nextStage}> Next </button>
</div>
This component renders children based on in what stage of registration the user is. I can receive callbacks from children in parent(children do set the state of the parent), but, when passing state.name from parent to child as a prop, the child receives the initial state, which means the name is empty string.
Child component:
class ContactMessage extends React.Component {
constructor(props){
super(props);
this.state ={
message:'',
name:''
}
this.handleChange=(event)=>{
this.props.onChange(event.target.value);
this.setState({message: event.target.value});
}
}
componentWillReceiveProps(props){
this.setState({name:props.name})
}
render(){
return(
<div>
<h1>{this.state.name}</h1>
<form onSubmit={this.handleSubmit}>
<label htmlFor='messageField'>
Message:
<input className='messageField' type="textfield" value={this.state.message}
onChange={this.handleChange} />
</label>
</form>
</div>
UPDATE: I am receiving initial props in the child components, not updated props from parent state. How do I receive new and updated props from parent state?
As I see, in ContactPage you trying to set event (e) as value of state.name instead of setting this.setState({ name: e.target.value }) in this. setName method.
By the way, you don't have to pass everything through constructor.
For example,
class MyComponent extends Component {
state = {
name: ''
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
}
render() {
return (
<input name="name" value={this.state.name} onChange={this.handleChange} />
);
}
}
Try extracting the stage array into its own method. Something like:
class ContactPage extends Component {
constructor(props) {
this.state = {
stage: 0,
name: 0
// and so on
}
}
getStage(index) {
let stages = [
<ContactName onChange={this.setName} />,
<ContactInfo />,
<ContactMessage name={this.state.name} onChange={this.setMessage} />,
<Send />
];
return stages[index];
}
render() {
return (
{this.getStage(this.state.stage)}
<AllTheOtherStuff />
)
}
}
3 things I'm noticing here, which may or may not be your problem.
First: Using state the way you are is inherently bug prone. If you are displaying this.state.name, and are setting name from this.props, both in the constructor and componentWillReceiveProps, then just skip the middle man and display this.props.name instead. Trying to maintain a correct state when the value in state is coming from props is really easy to mess up, thus it is better to just use props directly.
Second: I believe your problem is a mixture of 1 and 2 here, but you are declaring your stage variable in your constructor, so it is only going to use what it has available in the constructor. Move this.stage into your render() and it should display the correct state information.
Third: the way you are using componentWillReceiveProps can lead to confusion. It is better to name your variables something that accurately describes what they are, without possible confusion. I would change componentWillReceiveProps(props) to componentWillReceiveProps(nextProps) since nextProps is more explicit about what those props are you are dealing with.

How to allow updates to input without updating props in React?

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.

Resources