So say you have a child component that returns a number of input fields and takes
a data object as a prop.
class InputFields extends React.Component {
render() {
let data = this.props.data
return (
<div className='input-group'>
<input type='text' defaultValue={data.name} onChange={this.props.update} />
<input type='text' defaultValue={data.age} onChange={this.props.update} />
<input type='text' defaultValue={data.email} onChange={this.props.update}/>
</div>
)
}
}
And the parent component might look something like:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
items: getItemsFromSomewhere()
}
}
update(event) {
/* Somehow update main state here? */
}
render() {
/* imagine this as an array of objects, each with name, age, email ,etc. */
var items = this.state.items.map(item => {
return <InputFields data={item} update={this.update.bind(this)} />
})
return <div className='itemLists'> {items} </div>
}
}
Within this.props.update I want to set the corresponding state for the data object which was passed down. But the onChange function only receives the synthetic event which only provides a reference to the DOM element itself.
Whats the best way to find which state field needs to be updated, e.g., when the input field for data.name is changed, how can I propagate that up to the state within the main parent?
I know that refs could be used, but how can I know which ref to grab on the change event? Do I just have to iterate through all of them?
You could do it like this:
class InputFields extends React.Component {
_onChange(field) {
this.props.update(field, this.props.data);
}
render() {
let data = this.props.data
return (
<div className='input-group'>
<input type='text' defaultValue={data.name} onChange={this._onChange.bind(this, 'name')} />
<input type='text' defaultValue={data.age} onChange={this._onChange.bind(this, 'age')} />
<input type='text' defaultValue={data.email} onChange={this._onChange.bind(this, 'email')}/>
</div>
)
}
}
and then in the update function of the parent component you get the changed field as a string plus the whole data object:
update(field, item) {
// do stuff
var value = item[field];
}
edit: You were right I answered something dumb too quickly. Below is a brand new answer that'll fit your needs I think.
Why not using event.target.value ?
and you just use an event handler ?
In the child:
<input type='text' id="name" defaultValue={data.name} onChange={this.props.update("name")} />`
And the update function:
update(stateToChange) {
var self = this;
return function(e) {
var newState = {};
newState[stateToChange] = e.target.value;
self.setState(newState);
}
}
Hope it helped this time :)
Related
I have a Form component with a title input and a dynamic number of ingredient inputs (the user can add and remove them with corresponding buttons). Most of the form functions properly. However, I receive a warning that I have passed a value prop to a form field without an onChange handler for the ingredient input element(s).
In render() for my Form component, this part should dynamically generate inputs (EDIT: by mapping from an array of ingredients in my Form's state):
{this.state.ingredients.map((element, i) => {
return (
<div key={i}>
<input
type='text'
placeholder='new ingredient'
value={element || ''}
onChange={this.handleIngredientChange.bind(this)}
/>
</div>
);
})}
I thought these inputs would connect to my handleIngredientChange method to update the state of the ith ingredient name when the user changes the input value:
handleIngredientChange(i, event) {
let ingredients = [...this.state.ingredients];
ingredients[i] = event.target.value;
this.setState({ingredients});
}
And that this method would appropriately allow react to control each input element.
I seem to be misunderstanding this and/or .bind(), because I clearly assigned onChange={this.handleIngredientChange.bind(this)} to the ingredient input element in my map function. When the user types in one of these inputs, we get `TypeError: undefined is not an object (evaluating 'event.target').
Perhaps the root of the problem is that when the user types in the input, handleIngredientChange is not correctly set up to get the user's desired input value, but I cannot see where the error is.
I have looked at a lot of similar questions and have tried to implement their answers to no avail. Could anyone point me in the right direction on why I have not handled the onChange event properly? I'd be more than happy to explain my intentions further if you need me to. Many thanks in advance.
Full code:
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: '',
ingredients: [],
};
}
handleTitleChange(event) {
let title = this.state.title;
title = event.target.value;
this.setState({title});
}
handleIngredientChange(i, event) {
let ingredients = [...this.state.ingredients];
ingredients[i] = event.target.value;
this.setState({ingredients});
}
removeClick(i) {
let ingredients = [...this.state.ingredients];
ingredients.splice(i, 1);
this.setState({ingredients});
}
addClick() {
let ingredients = [...this.state.ingredients];
ingredients.push('');
this.setState({ingredients});
}
handleSubmit(event) {
console.log('submit request logged')
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
placeholder='recipe title'
value={this.state.title || ''}
onChange={this.handleTitleChange.bind(this)}
/>
{this.state.ingredients.map((element, i) => {
return (
<div key={i}>
<input
type='text'
placeholder='new ingredient'
value={element || ''}
onChange={this.handleIngredientChange.bind(this)}
/>
</div>
);
})}
<input
type='button'
value='add'
onClick={this.addClick.bind(this)}
/>
<input
type='Submit'
value='save'
onClick={this.handleSubmit.bind(this)}
/>
</form>
)
}
}
I'm not sure if this helps
try change
<input
type="text"
placeholder="new ingredient"
value={element || ""}
id={i}
onChange={this.handleIngredientChange.bind(this)}
/>
and then alter
handleIngredientChange(event) {
let ingredients = [...this.state.ingredients];
ingredients[event.target.id] = event.target.value;
this.setState({ ingredients });
}
https://codesandbox.io/s/react-playground-forked-yf7rpm
-Hello, I made a code to send props value from child component to parent component
in the child component I have multiple checkboxes with a value that I'll pass to the props, in addition of two functions, the first function to store the clicked inputs on array and pass it to the props and the second function to call the first function if the user check new input (this function used to update the props value )
export const ColorsForm = (props) => {
useEffect(()=>{
test()
},[])
// this function to store the checked colors in array then pass this array to the props
const getColors = () => {
const inpColor = document.querySelectorAll(".colorsInp");
let arrOfColors = [];
for(let i = 0; i < inpColor.length; i++){
if(inpColor[i].checked){
arrOfColors.push(inpColor[i].value);
}
}
props.array_of_colors(arrOfColors);
return arrOfColors;
}
// this function will be used to resend the prop value with new value
const whenNewColorClicked = () => {
const inpColor = document.querySelectorAll(".colorsInp");
for(let i = 0; i < inpColor.length; i++){
inpColor[i].addEventListener("click",()=>{
getColors();
})
}
}
return (
<div>
<label>Colors:</label>
<div>
<input type="checkbox" id="colorBlack" value="black" name='colorsInput' />
<label htmlFor="colorBlack" className='lblBlack'></label>
<input type="checkbox" id="colorWhite" value="white" name='colorsInput' />
<label htmlFor="colorWhite" className='lblWhite'></label>
<input type="checkbox" id="colorRed" value="red" name='colorsInput' />
<label htmlFor="colorRed" className='lblRed'></label>
<input type="checkbox" id="colorBlue" value="blue" name='colorsInput'/>
<label htmlFor="colorBlue" className='lblBlue'></label>
</div>
</div>
)
}
the parent component is where I'll valid the upcoming props to make a post request
import React, { useState ,useEffect } from 'react'
import { ColorsForm } from './colorsForm';
export const AddForm = () => {
const [colors, setColors] = useState([])
return (
<>
<ColorsForm array_of_colors={res => setColors(() => [...res])} />
<button onClick={postRequestFunction} >Insert</button>
</>
);
}
my code works fine but...
I know the child component code can be wrote better than I did, please feel free to notice anything, I'll be thankful
You do an "anti-pattern" here.
The parent must have the information and the children must display and interact with this information.
So the colors array must be define in the parent and your ColorsForm must .map the colors array props to display the different color in the form with the correct value.
Then, if you use document.querySelector is that there is a problem.
React manages the DOM and you should never do it for it. In your two functions, you should never listen to the click event by hand.
So I can't explain everything here, but I hope this information will help you improve your code. The best way to learn is to search and find answers by yourself
import FieldSect from "./fieldSect.js"
<div>
<FieldSect />
</div>
--FieldSect.js--
import Field from "./Field.js"
<div>
<Field />
<button onclick={addField}> addField </field>
</div>
--Field.js--
function Field (){
<div>
<label> Test </label>
<input type="text" />
</div>
}
My code works in the part where Field is loaded immediately and is shown correctly. I am really struggling on trying to figure out how to keep adding the <Field /> component under the already existing <Field /> whenever the add button is clicked. I also need to ensure to have the ability to keep adding rather than having a specific number of available
I also cannot use the DOM as I am getting an error telling me to edit the state rather than using DOM.render
End Result should look something like this:
--FieldSect.js--
<div>
<Field />
...<Field /> (Button Add Click)
...<Field /> (Button Add Click)
..
</div>
You should have Fields be part of the state of your parent component, perhaps as an array, this.state = {fields:[{id:1},{id:2},{id:3}]}.
Then, when rendering, you can iterate over the array:
<div>
{this.state.fields.map(f => <Field key={f.id}/>)}
</div>
Its super simple.
Have a default state of fields for example lets say that you want to have 1 field at the beginning
this.state = {
fields: [<Field/>]
}
Now use onClick event for AddClick and a function as follows
handleOnClick=(event)=>this.setState(prevState=>({...prevState, fields: [...prevState.fields, <Field/>]})
And in your render function iterate over fields array
PS: I am not sure what is a Field doing.
What I would do is in render
<div>
{this.state.fields.map(field => <Field {...field}/>)}
</div>
and fields would be the data of a field
If I'm understanding you correctly, you're wanting to add a every time someone clicks a button. If that's accurate, then I would simply add a constructor with a state to either the FieldSect.js or parent file to that (somewhere along the line before Field.js) that creates a trackable state. Something like:
this.state = {
NumberOfFields: 1
};
If you do this ahead of FieldSect.js, then it needs to be passed down in props.
Now you need to set up the onClick function to increment this state. Using this, you can then create a loop in FieldSect.js that creates an array of elements that will be rendered in React.
let array = []
for(let i = 0; i < this.NumberOfFields; i++){
array.push(<Field key={Math.random()}/>)
}
Then, instead of using in FieldSect.js, use {array}.
The reason I added the key attribute to the Field is that all elements that are derived from an iteration like this must have a unique key.
Here you go.
This holds the number of <Fields /> in the state of a controlled component, which can be incremented with the button.
The loop is generated by creating an Array of nbrOfFields length, destructured, and then mapped over.
const Field = () => (
<div>
<label> Test </label>
<input type="text" />
</div>
);
class App extends React.Component {
constructor() {
super();
this.state = {nbrOfFields: 1};
this.addField = this.addField.bind(this);
}
addField() {
this.setState(prevState => ({nbrOfFields: prevState.nbrOfFields + 1}));
}
render() {
return (
<div>
{[...Array(this.state.nbrOfFields)].map((item, i) => (
<Field key={i} />
))}
<button onClick={this.addField}>Add field</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I'm learning React and as a learning exercise am trying to do a very basic page where there is a form and you put text in an input box, you click submit and the header changes to what you entered. Here is my code so far:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {header: 'yeaheheh'}
}
changeHeader(e) {
let newHeader = document.getElementById('input').value();
e.preventDefault();
console.log('submitted');
this.setState(newHeader);
}
render() {
return (
<div>
<h1>{this.state.header}</h1>
<form onSubmit={this.changeHeader.bind(this)} className="change-header-form">
<input id="input" type="text" placeholder="Enter Text Here" />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default App;
At first, when I clicked submit, nothing happened and I got an error in the console that says
Uncaught TypeError: Cannot read property 'setState' of null
I then realized I needed to bind the changeHeader function to this which I changed so before I had:
<form onSubmit={this.changeHeader}...
changed it to
<form onSubmit={this.changeHeader.bind(this)}...
After doing this, the error cleared but my header is still not updating.I read that there has been strong suggestions against changing state via setState is bad practice because calling setState() again could potentially alter the changed state. setState is also an asynchronous operation which would also explain why my header isn't changing.
With all that said, then what would be the best way to handle this? From what I understand, props wouldn't make sense either since those values are stored directly in your component and aren't parameters that can't be dynamically updated. I'm having a hard time understanding the relationship between these different data types and how they are handled in the DOM.
You are setting state incorrectly.
More over to get the data from input fields you can either use controlled input elements(via states) or uncontrolled input elements via "ref" which I have used in below example.
In controlled input element you store the value of input element in state and changes to that value is done by calling onChange method and then setting the state via this.setState({}).
Calling setState causes re-rendering to happen and dom gets the updated data based on new state.
Btw "refs" gets you the direct access to dom elements, in similar way $() was used in jquery and should be avoided if possible because it will lead to very hard to manage and predict dom changes.
Also there are cases where use of "refs" is recommended
There are a few good use cases for refs:
Managing focus, text selection, or media playback.
Triggering imperative animations.
Integrating with third-party DOM libraries.
class App extends React.Component {
constructor() {
super();
this.state = {header: 'yeaheheh'};
}
changeHeader = (e) => {
e.preventDefault();
let newHeader = this.textInput.value;
console.log('submitted');
this.setState({header : newHeader});
}
render() {
return (
<div>
<h1>{this.state.header}</h1>
<form onSubmit={this.changeHeader} className="change-header-form">
<input id="input" ref={(input) => { this.textInput = input; }} type="text" placeholder="Enter Text Here" />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('test'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="test">
</div>
Replace this.setState(newHeader); with this.setState({header: newHeader});.
Take a look at this article in the react docs: https://facebook.github.io/react/docs/forms.html#controlled-components.
Basically what you want to do is create another handler for the input. This will be called every time there is a change to the input field and a property in your state will be updated. Then, when you submit the form you can take that new property and "merge" it using setState to become the new header.
JS Bin
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
header: 'yeaheheh',
next: ''
}
this.changeHeader = this.changeHeader.bind(this);
this.updateNext = this.updateNext.bind(this);
}
changeHeader(e) {
e.preventDefault();
this.setState({
header: this.state.next
});
}
updateNext(e) {
this.setState({
next: e.target.value
});
}
render() {
return (
<div>
<h1>{this.state.header}</h1>
<form onSubmit={this.changeHeader} className="change-header-form">
<input id="input" type="text" placeholder="Enter Text Here" onChange={this.updateNext} />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
Maybe this bin will provide a little better context at what I'm trying to describe.
There's the small bug in your code preventing it from working (this.setState(newHeader) -> this.setState({header: newHeader});), but the thing is that your code is not idiomatic for React.
You are supposed to use controlled components instead of grabbing the values from the form's inputs on submit, as you would do with jQuery.
"Controlled component" is a silly name for the pattern where an input's state is mapped to the application state, so an input itself behaves as if it would be kinda "stateless". In your case, you need to have separate component state member for every text input you've got. Input control should look like this:
<input value={ this.state.inputValue }
onChange={ e => this.setState({ inputValue : e.target.value }) }
/>
Now it's bound to your inputValue state member, so you can just take it from the state at any moment you need. On form's submit handler, in your case.
That's it. Your code must be fixed accordingly. Refer to the "controlled components" manual for further details, it's the really important React concept.
You should modified your function like this..
constructor(props) {
super(props);
_that = this;
}
changeHeader = (e) => {
e.preventDefault();
let newHeader = this.textInput.value;
console.log('submitted');
_that.setState({header : newHeader});
}
I have a large form with various form elements which is dynamically rendered from a get request. All other types of form (such as text and select) are working fine, however the checkbox is not.
After I check it once, it only stays on (even if I uncheck it), am I missing something or doing something wrong here?
Here is my current relevant code:
class Input extends Component{
render(){
var form;
if (this.props.componentClass=="choice") {
// select form
}
else if (this.props.componentClass=="bool")
form =(<Checkbox id={this.props.controlId} onChange={this.props.onChange}
defaultChecked={this.props.placeholder} >
</Checkbox>);
else
// text form
return (
<div>
<Form inline onSubmit={this.handleSubmit}>
<FormGroup controlId={this.props.controlId}>
<ControlLabel>{this.props.name}</ControlLabel>
{form}
<Panel>
{this.props.description}
</Panel>
<FormControl.Feedback />
</FormGroup>
</Form>
<br/>
</div>
);
}
}
// onChange code (comes from a parent component)
onChange(e){
const form = Object.assign({}, this.state.form);
form[e.target.id] = e.target.value;
this.setState({ form });
console.log('current state: ', this.state);
}
You must bind onChange function as said before, but you should use "checked" instead of "value".
Here is your example modified this way:
https://jsfiddle.net/8d3of0e7/3/
class Input extends React.Component{
constructor(props){
super(props)
this.state = {form:{}}
}
render(){
var form;
if (this.props.componentClass=="choice") {
// select form
}else if (this.props.componentClass=="bool"){
form = (
<ReactBootstrap.Checkbox
id={this.props.controlId}
onChange={this.props.onChange.bind(this)}
checked={this.state.form[this.props.controlId]}
defaultChecked={this.props.placeholder} >
</ReactBootstrap.Checkbox>);
}else{
// text form
}
return (
<div>
<ReactBootstrap.Form inline onSubmit={this.handleSubmit}>
<ReactBootstrap.FormGroup controlId={this.props.controlId}>
<ReactBootstrap.ControlLabel>
{this.props.name}
</ReactBootstrap.ControlLabel>
{form}
<ReactBootstrap.Panel>
{this.props.description}
</ReactBootstrap.Panel>
<ReactBootstrap.FormControl.Feedback />
</ReactBootstrap.FormGroup>
</ReactBootstrap.Form>
<br/>
</div>
);
}
componentDidUpdate(){
console.log('current state: ', this.state);
}
}
function onChange(e) {
const form = Object.assign({}, this.state.form);
form[e.target.id] = e.target.checked;
this.setState({ form });
}
ReactDOM.render(
<Input componentClass='bool' controlId='retired'
name='Is retired?' onChange={onChange}/>,
document.getElementById('root')
)
In this example our state will be: state:{form:{retired:true}}
The issue is that you gave the checkbox an onChange without binding it to a value. Therefor, the initial check is working since that is what the defaultChecked is doing for you, but once you actually interact with it, you have no state bound to it causing react to not rerender it in the checked or unchecked position.