How can I do a 2 way binding of a variable from the parent component (Form.js), such that changes occurred in the child component (InputText.js) will be updated in the parent component?
Expected result: typing values in the input in InputText.js will update the state of Form.js
In Form.js
render() {
return (
<div>
<InputText
title="Email"
data={this.state.formInputs.email}
/>
<div>
Value: {this.state.formInputs.email} // <-- this no change
</div>
</div>
)
}
In InputText.js
export default class InputText extends React.Component {
constructor(props) {
super(props);
this.state = props;
this.handleKeyChange = this.keyUpHandler.bind(this);
}
keyUpHandler(e) {
this.setState({
data: e.target.value
});
}
render() {
return (
<div>
<label className="label">{this.state.title}</label>
<input type="text" value={this.state.data} onChange={this.handleKeyChange} /> // <-- type something here
value: ({this.state.data}) // <-- this changed
</div>
)
}
}
You can manage state in the parent component itself instead of managing that on child like this (lifting state up):
In Form.js
constructor(props) {
super(props);
this.handleKeyChange = this.keyUpHandler.bind(this);
}
keyUpHandler(e) {
const { formInputs } = this.state;
formInputs.email = e.target.value
this.setState({
formInputs: formInputs
});
}
render() {
// destructuring
const { email } = this.state.formInputs;
return (
<div>
<InputText
title="Email"
data={email}
changed={this.handleKeyChange}
/>
<div>
Value: {email}
</div>
</div>
)
}
In InputText.js
export default class InputText extends React.Component {
render() {
// destructuring
const { title, data, changed } = this.props;
return (
<div>
<label className="label">{title}</label>
<input type="text" value={data} onChange={changed} />
value: ({data})
</div>
)
}
}
You can also make your InputText.js a functional component instead of class based component as it is stateless now.
Update: (How to reuse the handler method)
You can add another argument to the function which would return the attribute name like this:
keyUpHandler(e, attribute) {
const { formInputs } = this.state;
formInputs[attribute] = e.target.value
this.setState({
formInputs: formInputs
});
}
And from your from you can send it like this:
<input type="text" value={data} onChange={ (event) => changed(event, 'email') } />
This assumes that you have different inputs for each form input or else you can pass that attribute name also from parent in props to the child and use it accordingly.
You would need to lift state up to the parent
parent class would look something like
onChangeHandler(e) {
this.setState({
inputValue: e.target.value // you receive the value from the child to the parent here
})
}
render() {
return (
<div>
<InputText
title="Email"
onChange={this.onChangeHandler}
value={this.state.inputValue}
/>
<div>
Value: {this.state.inputValue}
</div>
</div>
)
}
children class would look something like
export default class InputText extends React.Component {
constructor(props) {
super(props);
this.state = props;
}
render() {
return (
<div>
<label className="label">{this.state.title}</label>
<input type="text" value={this.state.value} onChange={this.props.onChange} />
value: ({this.state.value})
</div>
)
}
}
You can simply pass a callback from Form.js to InputText and then call that callback in InputText on handleKeyChange
Related
How to update state of one component in another in react class component.
I have two class in reacts.
MyComponent and MyContainer.
export default class MyContainer extends BaseComponent{
constructor(props: any) {
super(props, {
status : false,
nameValue :"",
contentValue : ""
});
}
componentDidMount = () => {
console.log(this.state.status);
};
save = () => {
console.log("Hello I am Save");
let obj: object = {
nameValue: this.state.nameValue, // here I am getting empty string
templateValue: this.state.contentValue
};
// API Call
};
render() {
return (
<div>
<MyComponent
nameValue = {this.state.nameValue}
contentValue = {this.state.contentValue}
></MyComponent>
<div >
<button type="button" onClick={this.save} >Save</button>
</div>
</div>
);
}
}
MyComponent
export default class MyComponent extends BaseComponent{
constructor(props: any) {
super(props, {});
this.state = {
nameValue : props.nameValue ? props.nameValue : "",
contentValue : props.contentValue ? props.contentValue : "",
status : false
}
}
componentDidMount = () => {
console.log("MOUNTING");
};
fieldChange = (id:String, value : String) =>{
if(id === "content"){
this.setState({nameValue:value});
}else{
this.setState({contentValue:value});
}
}
render() {
return (
<div>
<div className="form-group">
<input id="name" onChange={(e) => {this.fieldChange(e)}}></input>
<input id = "content" onChange={(e) => {this.fieldChange(e)}} ></input>
</div>
</div>
);
}
}
In MyComponent I have placed two input field where on change I am changing the state.
Save button I have in MyContainer. In save button I am not able to read the value of MyComponent. What is the best way to achieve that.
You should be updating your state in MyContainer for save to have visibility of the state changes. Each component gets its own state, which makes MyComponent's state unique to that of MyContainer. What you should be doing is keeping the state in your parent/container component, and then passing it down as props (rather than duplicating it in your child). To do this, move fieldChange up to the MyContainer function, and remove the duplicate nameValue and contentValue state within MyComponent. See code commennts for further details:
export default class MyContainer extends BaseComponent{
...
fieldChange = (id:String, value : String) =>{
if(id === "content"){
this.setState({nameValue: value});
} else {
this.setState({contentValue: value});
}
}
render() {
return (
<div>
<MyComponent
nameValue={this.state.nameValue}
contentValue={this.state.contentValue}
onFieldChange={this.fieldChange} /* <---- Pass the function down to `MyComponent` */
/>
...
</div>
);
}
}
Then in MyComponent, call this.props.onFieldChange:
export default class MyComponent extends BaseComponent{
// !! this constructor can be removed as no state is being initialized anymore !!
constructor(props: any) {
super(props);
// removed state as we're using the state from `MyContainer`
}
componentDidMount = () => {
console.log("MOUNTING");
};
render() {
return (
<div>
<div className="form-group">
<input id="name" onChange={(e) => {this.props.fieldChange(e)}} /> /* <--- Change to `this.props.fieldChange()`. `<input />` is a self-closing tag.
<input id = "content" onChange={(e) => {this.props.fieldChange(e)}} />
</div>
</div>
);
}
}
Some additional notes:
If your component doesn't use this.props.children, then you should call it as <MyComponent ... props ... /> not <MyComponent ... props ...></MyComponent>
Your if-statement in your fieldChange looks reversed and should be checking if(id === "name"). I'm assuming this is an error in your question.
You're only passing one argument to fieldChange in your example code. I'm again assuming this in an error in your question.
I have a parent component which holds state that maintains what is being typed in an input box. However, I am unable to type anything in my input box. The input box is located in my child component, and the onChange and value of the input box is stored in my parent component.
Is there any way I can store all the form logic/input data on my parent component and just access it through my child components?
Here is a section of my parent component code:
export class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
screenType: 'init',
series: [],
isLoading: true,
title: '',
};
this.handleChange = this.handleChange.bind(this);
this.searchAPI = this.searchAPI.bind(this);
this.clickSeries = this.clickSeries.bind(this);
}
handleChange(e) {
this.setState({
value: e.target.value,
});
}
onKeyPress = (e) => {
if (e.which === 13) {
this.searchAPI();
}
};
async searchAPI() {
...some search function
}
render() {
return (
<Init onKeyPress={this.onKeyPress} value={this.state.value} onChange={this.handleChange} search={this.searchAPI} />
);
}
And here is a section of my Child component:
function Init(props) {
return (
<div className="container">
<div className="search-container-init">
<input
onKeyPress={props.onKeyPress}
className="searchbar-init"
type="text"
value={props.value}
onChange={props.handleChange}
placeholder="search for a TV series"></input>
<button className="btn-init" onClick={props.search}>
search
</button>
</div>
</div>
);
}
export default Init;
Incorrect function name used in the child. onChange prop passed into child in the parent and using that in the child as handleChange.
Also, you do not need to bind explicitly if using ES6 function definition.
Here is the updated code:
Search.js
export class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
screenType: 'init',
series: [],
isLoading: true,
title: '',
};
}
const handleChange = (e) => {
this.setState({
value: e.target.value,
});
}
const onKeyPress = (e) => {
if (e.which === 13) {
this.searchAPI();
}
};
const async searchAPI = () => {
...some search function
}
render() {
return (
<Init onKeyPress={this.onKeyPress} value={this.state.value} handleChange={this.handleChange} search={this.searchAPI} />
);
}
Child component:
function Init(props) {
return (
<div className="container">
<div className="search-container-init">
<input
onKeyPress={props.onKeyPress}
className="searchbar-init"
type="text"
value={props.value}
onChange={(e) => props.handleChange(e)}
placeholder="search for a TV series"></input>
<button className="btn-init" onClick={props.search}>
search
</button>
</div>
</div>
);
}
export default Init;
In your child component, you are using props.handleChange! But in your parent component, you passed it as onChange! Use the same name you used to pass the value/func. It should be like props.onChange! A silly error to watch out for
I'm currently working on a React application, where I two classes - let's call them class App and class Container. Basically, class App has a state array, and I want to have many Container objects in this array.
class Container extends React.Component{
render(){
return(
<img src= {this.props.url} />
);
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
url: ""
data: []
}
}
handleSubmit(event){
event.preventDefault();
//I WANT TO BE ABLE TO MAKE A NEW CONTAINER, AND PASS THE URL AS PROPS.
// THEN, I WANT TO ADD THAT CONTAINER TO THE ARRAY.
this.setState({
data: url = this.state.url, id = 'a'
});
}
render(){
return (
<form onSubmit={this.handleSubmit}>
<label htmlFor="url">url:</label>
<input
type = "text"
name = "url"
value = {this.state.url}
onChange = {this.handleChange}
/>
</form>
)
}
}
In the function handleSubmit() above, I want to add a new container containing the props URL to the array. How would I do this?
don't mutate the state
you just need url in the state, not the whole container
use setState to modify the state
consider using spread operator (...) for concatenation
I don't see handleChange in your code
class Container extends React.Component {
render() {
return <img src={this.props.url} />;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
url: "",
containers: []
};
}
handleChange = e => {
this.setState({
url: e.target.value
});
};
handleSubmit = event => {
event.preventDefault();
if (this.state.url) {
this.setState({
containers: [...this.state.containers, this.state.url],
url: ""
});
}
};
render() {
const { url, containers } = this.state;
return (
<div>
<form onSubmit={this.handleSubmit}>
<label htmlFor="url">url:</label>
<input
type="text"
name="url"
value={url}
onChange={this.handleChange}
/>
<button>submit</button>
</form>
<h2>Containers:</h2>
<div>
{!containers.length && <i>no urls added</i>}
{containers.map((_url, i) => (
<Container key={i} url={_url} />
))}
</div>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Working Example:
https://stackblitz.com/edit/react-g72uej
I try to pass state to the child, to update the list of objects.
When I add an entry, it's not rendered in the child component.
I also checked that state.contacts actually gets replaced with new array, but it didn't work.
constructor(props) {
this.super(props);
}
removeContact(event) {
this.setState((state) => {
state.contacts = state.contacts.filter((contact) => contact.key !== event.target.key )
return state;
})
}
render() {
return (
<Fragment>
<span>{this.props.contact.name}</span>
<span>{this.props.contact.phone}</span>
<span>{this.props.contact.adress}</span>
<a href="#" onClick={this.removeContact}>X</a>
</Fragment>
)
}
}
class Contacts extends Component {
constructor(props) {
super(props);
this.state = { contacts: props.contacts };
}
render() {
console.log(this.state.contacts); // always displays empty array
return (
<div>
{this.state.contacts.map((contact, index) =>
<div>
<Contact key={index} contact={contact} contacts={this.state.contacts}/>
</div>
)}
</div>
)
}
}
class App extends Component {
state = {
time: new Date(),
name: "",
phone: "",
adress: "",
contacts: []
}
change = (event) => {
let nameOfField = event.target.name;
this.setState({[nameOfField]: event.target.value})
}
// click = () => {
// this.setState((state) => {
// state.time = new Date();
// return state;
// })
// }
addContact = () => {
let name = this.state.name;
let phone = this.state.phone;
let adress = this.state.adress;
this.setState((state) => {
return {contacts: [ ... state.contacts.concat([{name, adress, phone}])]}
});
}
render() {
return (
<div className="App">
<Timestamp time={this.state.time}/>
<Contacts contacts={this.state.contacts}/>
<input name="name" value={this.state.name} onChange={this.change} placeholder="Name"/>
<input name="phone" value={this.state.phone} onChange={this.change} placeholder="Phone"/>
<input name="adress" value={this.state.adress} onChange={this.change} placeholder="Adress"/>
<button onClick={this.addContact}>Add contact</button>
</div>
)
}
}
ReactDOM.render(<App time={Date.now().toString()}/>, document.getElementById('root'));
If values are passed to Components you should render them as props. There is no need to copy into the child component state:
class Contacts extends Component {
render() {
console.log(this.props.contacts); // use props instead of state
return (
<div>
{this.props.contacts.map((contact, index) =>
<div>
<Contact key={index} contact={contact} contacts={this.props.contacts}/>
</div>
)}
</div>
)
}
}
Using this.props is good practice because it allows React to deterministically render (If the same props are passed, the same render result is returned).
You are currently modifying the state in Contacts from it's child component Contact. You can't update a parents state directly from within a child component.
What you could do is create a removeContact function in your Contacts component and pass the entire function down to your Contact component. That way when you call removeContact in your child component, it will actually call it from the parent, modify the parents state, and update all it's children with the new state.
I have the following parent component:
class NewPetForm extends React.Component {
state = {
name: '',
age: '',
animal: '',
breed: ''
};
render() {
return (
<StyledNewPetForm >
<Input label="name" />
<Input label="age" />
<Input label="animal"/>
<Input label="breed"/>
<Button type="submit" />
</StyledNewPetForm>
);
}
}
And the following child component:
class Input extends React.Component {
render() {
return (
<StyledWrapper>
<StyledInput
value={this.props.test}
type="text"
/>
</StyledWrapper>
);
}
}
Is it possible to listen to changes in all inputs in children components and update the state accordingly?
What I want to achieve is basically passing handlers to different Inputs and update state dynamically, so there is a possibility to reuse Input component.
changeHandler = (event,stateName) =>{
this.setState({[stateName]:event.target.value]})
}
<Input changed={changeHandler(event,'name')}></Input>
<Input changed={changeHandler(event,'age')}></Input>
<Input changed={changeHandler(event,'breed')}></Input>
// Inside Input
<input onChange={this.props.changed}/>
Thanks for help!
Yes you can do that, you can pass a function to the child element as a property and call it when a change occurs in the child.
Here is an example:
class NewPetForm extends React.Component {
state = {
name: ''
};
onValueChange(key, event) {
this.setState({[key]: event.target.value})
}
render() {
return (
<StyledNewPetForm >
<Input value={this.state.name} onValueChange={this.onValueChange.bind(this, 'name')} />
</StyledNewPetForm>
);
}
}
and the child
class Input extends React.Component {
render() {
return (
<StyledWrapper>
<StyledInput
onChange={this.props.onValueChange}
value={this.props.value}
type="text"
/>
</StyledWrapper>
);
}
}
{[key]: event.target.value} may seem confusing, is just the new syntax that lets you use a string as a property name in an object literal.
The important part is onChange={this.props.onValueChange}, here, I'm calling the parent function NewPetForm.onValueChange when the input's value changes.