I have a main class here that holds three states rendering a Form component:
class MainApp extends React.Component{
constructor(props){
super(props);
this.state = {
fName: '',
lName: '',
email: ''
}
render(){
return(
<div className="content">
<Form formState={this.state}/>
</div>
)
}
}
And then inside my Form component I have ff codes:
class Form extends React.Component{
constructor(props){
super(props);
}
render(){
return(
<form>
<input placeholder="First Name" value={this.props.formState.fName}
onChange={e => this.setState({ this.props.formState.fName: e.target.value })}
</form>
)
}
}
Upon running this codes, I got an error saying it cannot read the property 'fName' of null.
How do I properly pass one state to it's children component so it can modify the main one?
You can't edit parent's state directly from child component. You should define handlers that would change parent's state in parent component itself, and pass them to children via props.
Parent:
class MainApp extends React.Component{
constructor(props){
super(props);
this.state = {
fName: '',
lName: '',
email: ''
}
fNameOnChange = (value) => {
this.setState({fName:value})
}
render(){
return(
<div className="content">
<Form formState={this.state} fNameChange={this.fNameOnChange}/>
</div>
)
}
}
Child:
class Form extends React.Component{
constructor(props){
super(props);
}
render(){
return(
<form>
<input placeholder="First Name" value={this.props.formState.fName}
onChange={e => this.props.fNameChange(e.target.value))}
</form>
)
}
}
class MainApp extends React.Component{
constructor(props){
super(props);
this.state = {
fName: '',
lName: '',
email: ''
}
this._handleChange = this._handleChange.bind(this);
}
//dynamically update the state value using name
_handleChange(e) {
const { name, value } = e.target;
this.setState({
[name]: value
});
}
render(){
return(
<div className="content">
//You can pass state and onchange function as params
<Form formState={this.state} _handleChange={this._handleChange}/>
</div>
)
}
}
class Form extends React.Component{
constructor(props){
super(props);
}
render(){
return(
<form>
<input placeholder="First Name"
defaultValue={this.props.formState.fName}
id="fName"
name="fName"
onChange={e => this.props._handleChange} />
</form>
)
}
}
You should pass a function as a prop to update the state of parent component. It is advised not to change props directly.
In your parent class write a function like:
onChangeHandler (data) {
this.setState({fname: data})
}
And pass it to the child component and you can call this method as
this.props.onChangeHandler(event.target.data)
from child component.
Thanks
I'm wondering why you want to pass a formState to the form itself. The form should manage is own state. It is not a good practice to do 2 way binding like the Form mutate the MainApp's state
However, if for some reasons you need a copy of you formState in your MainApp component, then your Form component should have an onSubmit callback method.
class MainApp extends React.Component{
constructor(props){
super(props);
this.state = {
form: null
}
}
render() {
return(
<div className="content">
<Form onSubmit={(form) => {
this.setState({ form });
})}/>
</div>
)
}
}
class Form extends React.Component{
constructor(props){
super(props);
this.state = {
fName: '',
lName: '',
email: ''
}
}
render(){
return(
<form onSubmit={e => this.props.onSubmit(this.state) }>
<input placeholder="First Name" value={this.props.formState.fName}
onChange={e => this.setState({ this.props.formState.fName: e.target.value })}
</form>
)
}
}
Related
I have a textarea where I want to have an onChange event. When text is entered, I want to compute the length of the string entered and then pass it to another react component to display the character count. However, I'm having trouble passing the data into my react component.
I have 3 react components in total:
SegmentCalculator: this is my full app
InputBox: this is where a user would enter their string
CharacterBox: this is where I'd like to display my character count
Here's what I have so far:
class InputBox extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
}
}
render() {
return (
<label>
Input:
<textarea
type="text"
value={this.state.value}
onChange={() => this.props.onChange(this.state.value)}
/>
</label>
);
}
}
class CharacterBox extends React.Component {
render() {
return (
<div>Character Count:{this.props.charCount}</div>
)
}
}
class SegmentCalculator extends React.Component {
constructor(props) {
super(props);
this.state = {
inputChars: null,
};
}
handleChange(value) {
this.setState({
inputChars: value,
inputCharsLength: value.length,
});
}
render() {
let charaterCount = this.state.inputCharsLength;
return (
<div className="segment-calculator">
<div className="input-box">
<InputBox onChange={() => this.handleChange()} />
</div>
<div className="characters">
<CharacterBox charCount={charaterCount}/>
</div>
</div>
);
}
}
You have a semi-controlled input, meaning, it has local state but you don't update it.
Pass the input state from the parent.
InputBox - Pass the value prop through to the textarea element. Pass the onChange event's target value to the onChange callback prop.
class InputBox extends React.Component {
render() {
return (
<label>
Input:
<textarea
type="text"
value={this.props.value}
onChange={(e) => this.props.onChange(e.target.value)}
/>
</label>
);
}
}
SegmentCalculator - Pass this.state.inputChars to the InputBox value prop. The input length is derived state so there is no reason to store it in state.
class SegmentCalculator extends React.Component {
constructor(props) {
super(props);
this.state = {
inputChars:'',
};
}
handleChange = (value) => {
this.setState({
inputChars: value,
});
}
render() {
const { inputChars } = this.state;
return (
<div className="segment-calculator">
<div className="input-box">
<InputBox
onChange={this.handleChange}
value={inputChars}
/>
</div>
<div className="characters">
<CharacterBox charCount={inputChars.length}/>
</div>
</div>
);
}
}
class InputBox extends React.Component {
render() {
return (
<label>
Input:
<textarea
type="text"
value={this.props.value}
onChange={(e) => this.props.onChange(e.target.value)}
/>
</label>
);
}
}
class CharacterBox extends React.Component {
render() {
return (
<div>Character Count:{this.props.charCount}</div>
)
}
}
class SegmentCalculator extends React.Component {
constructor(props) {
super(props);
this.state = {
inputChars: '',
};
}
handleChange = (value) => {
this.setState({
inputChars: value,
});
}
render() {
const { inputChars } = this.state;
return (
<div className="segment-calculator">
<div className="input-box">
<InputBox
onChange={this.handleChange}
value={inputChars}
/>
</div>
<div className="characters">
<CharacterBox charCount={inputChars.length}/>
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<SegmentCalculator />,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" />
I am unable to print the Input-
Problem- I give an input and click on the button. I want the input to be visible in the next line.
react
import React from 'react';
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
event.preventDefault()
this.setState({value: event.target.value})
}
render() {
return (
<div>
<form onSubmit= {this.handleChange}>
<input type="text"/>
<button>Submit</button>
</form>
<h1>{this.state.value}</h1>
</div>
)
}
}
export default NameForm;
Your passing change handler to submit, this won't work.
You have to keep track of the inputs with state the way I did or you can use Refs to get the values of the inputs.
Your issue could be solved like this
import React from "react";
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = { value: "", show: false };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
//keep track of input changes
this.setState({ value: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
this.setState({ show: true });
// handle form submission here
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} type="text" />
<button>Submit</button>
</form>
{this.state.show ? (
<div>
<h1>{this.state.value}</h1>
</div>
) : null}
</div>
);
}
}
export default NameForm;
I was developing a react component to get a value inside a input and automatically show it in a tag, using refs.
All works fine, but the value shown is the previous value.
I really don't now how to fix this. I using the onChange event in the input to change the state of what will be shown, it is clear that the present value is not taken, but rather the previous value
class Conversor extends Component {
constructor(props){
super(props)
this.state = {
value: null
}
this.output = this.output.bind(this)
}
output(){
console.log(this.state)
this.refs.output.innerHTML = this.state.value
}
render() {
return (
<div>
<h2>{this.state.inputValue}</h2>
<input ref="input" type="text" onChange={() => {this.setState({ value: this.refs.input.value }); this.output()}}/>
<label ref="output"></label>
</div>
);
}
}
If i put the value "Hello World" in the input, the value shown is "Hello Worl", when it's have to be the "Hello World"
You can use event to do this and no need of output() function.
class Conversor extends Component {
constructor(props){
super(props)
this.state = {
value: null
}
}
render() {
return (
<div>
<h2>{this.state.inputValue}</h2>
<input ref="input" type="text" onChange={(e) => {this.setState({ value: e.target.value });}}/>
<label ref="output">{this.state.value}</label>
</div>
);
}
}
The best way to achieve your goal is not using the refs. Here is how you do it
class Conversor extends Component {
constructor(props){
super(props)
this.state = {};
}
handleChange = (e) => {
const { id, value } = e.target;
this.setState({
[id]: value
})
}
render() {
const { name, anotherName } = this.state;
return (
<div>
<h2>{name}</h2>
<input id="name" name="name" type="text" onChange={this.handleChange}/>
<h2>{anotherName}</h2>
<input id="anotherName" name="anotherName" type="text" onChange={this.handleChange}/>
</div>
);
}
}
If you still want to use the refs then do the following,
class Conversor extends Component {
constructor(props){
super(props)
this.state = {
value: null
}
}
output = (e) =>{
this.setState({value: e.target.value }, () => {
this.refs.output.innerHTML = this.state.value
})
}
render() {
return (
<div>
<input ref="input" type="text" onChange={this.output}/>
<label ref="output"></label>
</div>
);
}
}
You don't need to bind your input handler function at all. Instead of doing that, just use an arrow function like _handleInputTextChange . Check this out:
import React, { Component } from 'react';
class InputTextHandler extends Component {
constructor(props){
super(props)
this.state = {
inputValue: ''
}
}
_handleInputTextChange = e => {
const inputValue = e.target.value;
this.setState({inputValue})
console.log(inputValue)
}
render() {
return (
<div>
<input
type="text"
onChange={this._handleInputTextChange}/>
</div>
);
}
}
export default InputTextHandler;
Two things: grab the event value in the onChange method, and pass the this.output method as the second argument to setState which fires after the state has been updated which is not a synchronous operation.
render() {
return (
<div>
<h2>{this.state.inputValue}</h2>
<input ref="input" type="text" onChange={event => {this.setState({ value:event.target.value }, this.output)}}/>
<label ref="output"></label>
</div>
);
}
Try it here!
Why in this example child component changing parent component state? According to the Facebook(react.js) docs State is similar to props, but it is private and fully controlled by the component.
codepen example
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {data: this.props.data};
}
handleChange(event) {
let updatedData = Object.assign({}, this.state.data);
updatedData[event.target.name][event.target.dataset.lang] = event.target.value;
this.setState({
data: updatedData
});
}
render() {
return (
<form>
{Object.keys(this.props.data.titles).map((l, index) =>
<input type="text" name="titles" data-lang={l} value={this.state.data.titles[l]} onChange={this.handleChange.bind(this)} />
)}
</form>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
images: [{"titles": {"en": "deluxe1500x930.jpg"}
}],
count: 1
};
}
render() {
return (
<div>
{Object.keys(this.state.images).map((x,index) =>
<div>
{this.state.images[x].titles.en}
<NameForm data={this.state.images[x]} />
<button onClick={(() => {this.setState({ count: 2 })}).bind(this)}>test</button>
</div>
)}
</div>
)
}
}
Because you set the state with this.props.data.
the this.props.data came from the parent, therefore when it's changing so the state changes as well.
The solution is simple, just set the state with new value (copied from this.props.data) by using the spread operator instead of using the same reference.
this.state = {data: ...this.props.data};
Below is a simple case with three components: App, ExmpleDataConsumer, and ExampleForm. App contains the other two.
I want the contents of the textarea in ExampleForm to be transmitted to ExampleDataConsumer's state when the form is submitted. Setting ExampleDataConsumer's state in doParse triggers a render which seems to be causing the form to submit, reloading the page, even though the handleSubmit method of ExampleForm calls event.preventDefault().
If I just log data to the console, preventDefault() works and the page does not refresh. What am I missing? Is this the wrong way to pass state between sibling components? Any help would be most appreciated.
class App extends React.Component {
constructor(props) {
super(props);
this.exampleForm = <ExampleForm doSomething = {this.doParse.bind(this)} />;
this.exampleDataConsumer = <ExampleDataConsumer data="Hello, World!"/>;
}
doParse(data) {
console.log('In App: ', data);
this.exampleDataConsumer.setState({data: data});
}
render() {
return (
<div className="App">
{this.exampleForm}
{this.exampleDataConsumer}
</div>
);
}
}
class ExampleDataConsumer extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
}
render() {
return ( <div>{this.state.data}</div>)
}
}
class ExampleForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Some starter text.'
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
this.props.doSomething(this.state.value);
this.setState({value: ''});
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
<textarea rows="20" cols="200" value={this.state.value} readOnly/>
</label>
<input type="submit" value="Parse" />
</form>
);
}
}
In handleSubmit(event), you should first call the preventDefault() method.
handleSubmit(event) {
event.preventDefault();
this.props.doSomething(this.state.value);
this.setState({value: ''});
}
check this update something in your code . i hope this is helpful for you
class App extends React.Component {
constructor(props) {
super(props);
this.state={
data:'Hello, World!'
}
}
doParse(data) {
console.log('In App: ', data);
this.setState({data: data});
}
render() {
return (
<div className="App">
<ExampleForm doSomething = {this.doParse.bind(this)} />
<ExampleDataConsumer data={this.state.data}/>
</div>
);
}
}
class ExampleDataConsumer extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
}
componentWillReceiveProps(nextProps) {
this.setState({data:nextProps.data});
}
render() {
return ( <div>{this.state.data}</div>)
}
}
class ExampleForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
this.dataUpdate = this.dataUpdate.bind(this);
}
handleSubmit(event) {
this.props.doSomething(this.state.value);
this.setState({value: ''});
event.preventDefault();
}
dataUpdate(ev){
this.setState({value:ev.target.value });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
<textarea rows="20" cols="200" placeholder="Some starter text." value={this.state.value} onChange={this.dataUpdate.bind(this)}/>
</label>
<input type="submit" value="Parse" />
</form>
);
}
}