I am relatively new to ReactJs.I am learning react while I am trying to create a real world app. Here is something I cannot solve.
I have a repeated component that has one input and one button.
everytime the button is clicked, the value of the input will be used in one function.
In Angular I do not have to worry about how to passing those value since in ngFor we can directly assign the value from the ngModel. But there is no such concept in React.
betOnTeam = (_id, coins) => {
return;
};
{this.teamList.map(team => (
<div key={team._id}>
<input type="number" min="100" max="5000" />
<button type="button"
onClick={() => this.betOnTeam(team._id,//value from the
input above)}>
</div>
))}
So basically I Have a function ready to receive an Id and how many coins the user bet.
And As we can see from the picture, I have many inputs which should contain the value of how much coins the user put for a specific team.
each button will trigger this betOnTeam function and will pass the unique Id of the team, and the number coins the user bet.
How can I set states for all thoese teams since they are all dynamic, it could be 5 teams or 100 teams. Is it any way to do it dynamically?
e.g. user input 5000, when he click the button, the id and the value will be passed into the function betOnTeam.
I hope this clarified my question.
==================================
Thanks for all the input from you guys.
I have make it working combine with all your suggestions.
So Here is what I do:
betOnTeam = (event, id) => {
console.log(event.target[0].value, id);
return;
};
{this.teamList.map(team => (
<form key={team._id} onSubmit={(e) => this.betOnTeam(e,team._id)}>
<input type="number" min="100" max="5000" />
<button type="submit">
</form >
))}
Seems like you're really close. I think this ultimately comes down to how you want to construct your components. There is an easy way to do this (the more React) way, and there is a hard way.
The easy way is to split the mark-up created inside the .map() into its own component. You will have an individual component for each team, thus the state is encapsulated to its own component. By doing this you can effectively keep track of the inputs for each team.
Consider this sandbox for example: https://codesandbox.io/s/dazzling-roentgen-jp8zm
We can create a component for the markup like this:
Team
import React from "react"
class Team extends React.Component {
state = {
betValue: 100
};
handleOnChange = e => {
this.setState({
betValue: e.target.value
});
};
handleOnClick = () => {
this.props.betOnTeam(this.state.betValue, this.props.id);
};
render() {
const { id } = this.props;
const { betValue } = this.state;
return (
<div key={id}>
<input
type="number"
min="100"
max="5000"
value={betValue}
onChange={this.handleOnChange}
/>
<button onClick={this.handleOnClick} type="button">
Bet
</button>
</div>
);
}
}
export default Team;
So from a purely jsx standpoint, the markup is the same, but now it is contained inside a class-component.
Now we can keep track of the inputs in a controlled manner.
When we're ready to place the bet, the value is stored in the
individual component state.
We pass down two properties to each Team component, the team_id and
betOnTeam function. The team_id can be accessed using this.props.id and likewise we will pass it into this.props.betOnTeam() when required.
Main Component
import React from "react"
import Team from "./Team"
class App extends React.Component {
teamList = [
{ team_id: 1, name: "TSM" },
{ team_id: 2, name: "SKT" },
{ team_id: 3, name: "CLG" }
];
betOnTeam = (betValue, teamId) => {
console.log(betValue);
console.log(teamId);
};
render() {
return (
<div>
{this.teamList.map(team => (
<Team id={team.team_id} betOnTeam={this.betOnTeam} />
))}
</div>
);
}
}
So the .map() renders a Team component for each team and passes in their respective ids and the betOnTeam function as props. When the button inside the component is clicked, we can pass back up the values stored in the Team Component to execute betOnTeam.
onClick={this.betOnTeam(form._id,value)}
Don't execute this.betOnTeam right from the start, you're actually setting the click handler to the returned result of this.betOnTeam(form._id,value). In React, it's not quite the same as in Angular. For this, you need to set it equal to a function that does that. Like this:
onClick={() => this.betOnTeam(form._id,value)}
Hope this helps.
1. this.betOnTeam = (_id, value) => { ... }
2. constructor(props) { this.betOnTeam.bind(this) }
3. onClick = { () => this.betOnTeam(form._id, value)}
Well if you use onClick = { this.betOnTeam(form._id, value) }, then the code will be executed first, and in betOnTeam function, you will not use 'this' operator.
But if you use the above methods, then you can use 'this' in the function and get the good results.
And your code has some bugs to fix,
{this.array.map(form => (
<div key={form._id}>
<input name={`editform{form._id}`} type="number" min="100" max="5000" onChange={(e) => this.changeNumber(e, form._id) }/>
<button type="button"
onClick={this.betOnTeam(form._id,value)}>
</div>
))}
And in changeNumber function, you should use setState({}) function to set the value to the state, and in betOnTeam function, you can use the state you have already set.
The code must be like this, or otherwise you can use ref but it is not formally encouraged to use ref.
Totally, you should use ControlledComponent. That's the target.
I hope you to solve the problem.
Related
Dynamic forms with react and antd are eluding me. I have scoured the web looking for answers to no avail. Here is a codepen with a recreation of the issue I am having: https://codepen.io/sethen/pen/RwrrmVw
Essentially, the issue boils down to when you want loop through a bunch of values that are stored in state, like so:
class MyClass extends React.Component<{}, {}> {
constructor(props) {
super(props);
this.state = {
data: [
{ name: 'foo' },
{ name: 'bar' },
{ name: 'baz' }
]
};
}
You can think of these values as being fetched from some remote API.
As you can see, I have an array of objects with the key of name in the state. Further on down in the render cycle is the following:
return data.map((value, index) => {
const { name } = value;
return (
<Form key={ index } initialValues={ { name } }>
<Form.Item name='name'>
<Input type='text' />
</Form.Item>
<Button onClick={ this.handleOnDeleteClick.bind(this, index) }>Delete</Button>
</Form>
);
This attempts to loop through the values stored in the state and put the values into an input. It also adds a little delete button to get rid of that item. The first time it renders, it does as you expect it to loading the value into the input value.
The issue is when you try to delete one of the items, like the middle one, it will delete the next item. The core of the issue is that the render is acting different than I expect it to when deleting an item. I am expecting that when I delete an item, it will take it out of state and load the ones that are left. This is not happening.
My question is, how am I able to load dynamic data in this way with antd whilst being able to delete each item?
The main mistake in this form that you assign the key property as the array index, and on deleting the middle item, the last component will get a new key.
In React, changing the key will unmount the component and lose its state.
Don’t pass something like Math.random() to keys. It is important that keys have a “stable identity” across re-renders so that React can determine when items are added, removed, or re-ordered. Ideally, keys should correspond to unique and stable identifiers coming from your data, such as post.id.
Also, in your example, you actually render three forms instead of a single form and three fields.
Every <form/> has in its inner state all states of its form fields, so you will have a single object with all input values in it.
Antd.Form just a wrapper for such form, you can get Form.Item values in onFinish callback for example.
class MyClass extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [{ name: "foo" }, { name: "bar" }, { name: "baz" }]
};
}
handleOnDeleteClick = index => {
this.setState({
data: [
...this.state.data.slice(0, index),
...this.state.data.slice(index + 1)
]
});
};
render() {
const { data } = this.state;
return (
<Form>
{data.map(({ name }, index) => {
return (
<Form.Item key={name}>
<Input type="text" />
<Button onClick={() => this.handleOnDeleteClick(index)}>
Delete
</Button>
</Form.Item>
);
})}
</Form>
);
}
}
So I am in a situation where I have to change a particular property from an array of objects. When the property changes I want to rerender the component. Now, this works fine without any issues when use the setPropertyName of useState. But now I am just changing one property of the object instead of the entire object.
Here is the code that Im working on:
const [movieList, setMovieList] = useState([]);
Calling the setMovieList and passing an array will obviously cause a rerender.
Consider the following contents of movieList:
movieList = [
{
'name': 'Mulholland Dr.'
'year':2001,
'watched' : true,
'rating':0
},
{
'name': 'Zodiac'
'year':2007,
'watched' : false,
'rating':0
},
{
'name': 'Twin Peaks'
'year':2017,
'watched' : true,
'rating': 0
}]
Then I have a function which renders the list:
function showMovieList () {
return movieList.map((movie) => {
return (
<List.Item key={movie.imdbID}>
<div className="watchedCheckBoxContainer">
<input type="checkbox" onChange={(event) => movie.watched = event.target.checked} id={`cb1${movie.imdbID}`}/>
<label htmlFor={`cb1${movie.imdbID}`}><Image size='tiny' src={movie.Poster} /></label>
</div>
{/* <Image size='tiny' src={movie.Poster} /> */}
<List.Content>{movie.Title}</List.Content>
{movie.watched ? <Rating maxRating={5} onRate={(event, {rating}) => movie.userRating=rating}/> : null}
</List.Item>
)
});
}
As you can see , when the checkbox is clicked it changes the value of the watched property. A few lines later I'm checking if movie.watched == true then show the <Rating> component. But in this case, I'm not using setMoviesList to update the moviesList and hence the <Rating> component is not visible.
How can I use setMoviesList to update watched property of the particular movie whose checkbox I click on?
Okay.. I solved it by the following way:
function onMovieWatched (watched, index) {
const tempMoviesList = [...movieList];
tempMoviesList[index].watched = watched;
setMovieList(tempMoviesList);
}
<input type="checkbox" onChange={(event) => onMovieWatched(event.target.checked, idx)} id={`cb1${movie.imdbID}`}/>
The idx is the index that I am using from the map method.
Initially I was afraid that I might have to loop over the entire array and get the object that matches the imdbID and then update its property.
Luckily I have the index while mapping over it, so I just used that to directly retrieve the object.
Dont know why I didnt think of this solution before posting.
I'm trying to find a neater way to handle this pattern I keep coming across with react when handling changes for form fields.
For each element of my form object that I handle a change in value for I find myself replicating this pattern quite a bit with the setter function of useState(). I've tried a couple of things like creating shallow copies of the formState and mutating that but the only way I can really get things to work is with the bellow pattern which feels a little repetitive.
const handleTitle = evt => {
props.setFormState({
title: evt.target.value,
bio: props.formState.bio,
formExpertise: props.formState.formExpertise,
formExpertiseYears: props.formState.formExpertiseYears
});
};
If you want to include this.props.formState you can spread the object into the new state. Further, you can use the input’s name as the state key so you don’t have to rewrite this for every input:
props.setFormState({
...this.props.formState, // copy props.formState in
[evt.target.name]: evt.target.value // use input name as state key
});
Suggestion:
You might consider moving the state merging up into the parent component:
// parent component
const [formState, setFormState] = React.useState({});
const onFieldChange = (field, value) => {
setFormState({
...formState,
[field]: value
});
}
return (
<MyFormComponent
formState={formState}
onFieldChange={onFieldChange}
/>
);
Each input can then invoke onFieldChange with the field name and value without concerning itself with the rest of the state:
function MyFormComponent ({onFieldChange}) {
const handler = ({target: {name, value}}) => onFieldChange(name, value);
return (
<div>
<input name="title" value={formState.title} onChange={handler} />
<input name="bio" value={formState.bio} onChange={handler} />
<input name="expertise" value={formState.expertise} onChange={handler} />
</div>
);
}
I'm in the early process of building a commissioning checklist application for our company. Since the checklist is fairly large (and many of them) I wanted to create a function that maps through an object and after rendering the values written would update the appropriate states with a useState Hook.
The page is rendering without any issues. The problem only appears once the input is changed. Instead of updating the correct state in the object. It seems the logic is adding an additional section in my object and creating another input element.
import React, { useState } from 'react'
const ProjectInfo = () => {
const _renderObject= () => {
return Object.keys(answers).map((obj, i) => {
return(
<div key={obj}>
<label>{answers[obj].question}</label>
<input type="text" onChange={(e, obj) => setAnswer(
{
...answers,
obj:{
value: e.target.value
}})} />
</div>
)
})
}
const [answers, setAnswer] = useState({
11:{
question:"Project Name",
value:""
},
12:{
question:"Project Number",
value:""
}
})
return(
<div>
<section>
{_renderObject()}
</section>
<p>{`Project Number is: ${answers[11].value}`}</p>
<p>{`Project Name is: ${answers[12].value}`}</p>
</div>
)
}
export default ProjectInfo
I was expecting for the state to just update as normal. But what I'm suspecting is in my renderObject method my obj variable for my .map function is not being used inside my setAnswer function and causes another field to be created with a key name of "obj".
If this is the issue is it possible to have the setAnswer function in my renderObject Method to use the "obj" value of the map function and not the actual value of the word obj as key?
If not what would be the best way to approach this? I was thinking of adding a submit button at the bottom of the screen and updating all states with an onClick event listener. But now I'm think I'll have the same issue since the scope of the obj variable isn't resolved.
Any help would be greatly appreciated. I've only been doing this for a couple of months, any advice and feedback would also be appreciated!
You seem to be not using the dynamic key correctly while updating state. Also you need to update the value within the key and not override it. Also obj shouldn't be the second argument to onChange instead it must be received from the enclosing scope
const _renderObject= () => {
return Object.keys(answers).map((obj, i) => {
return(
<div key={obj}>
<label>{answers[obj].question}</label>
<input type="text" onChange={(e) => setAnswer(
{
...answers,
[obj]:{
...answers[obj],
value: e.target.value
}})} />
</div>
)
})
onChange={(e, obj) => setAnswer(
{
...answers,
obj:{
value: e.target.value
}})}
Here you spreading answers and add another object with the target value. that is the issue. Hope you understand the point.
TRY THIS
onChange={
(e, obj) => {
const updatedAnswer = answer.map(ans => ans.question === obj.question ? {...ans,value: e.target.value }:ans)
setAnswer(
{
...updatedAnswer
}
)
}
}
BW your object should contain propper ID for the key.
Its because you are not updating the keys correctly and you need to pass obj in input onchange callback as it make another reference, not the mapped array(obj). So in your case that obj is undefined. Here is working code :
const _renderObject = () => {
return Object.keys(answers).map((obj, i) => {
return (
<div key={obj}>
<label>{answers[obj].question}</label>
<input
type="text"
onChange={e =>
setAnswer({
...answers,
[obj]: { //take obj
...answers[obj],//keep specific object question
value: e.target.value//change only specfic object value
}
})
}
/>
</div>
);
});
};
Here is working url: https://codesandbox.io/s/hardcore-pike-s2hfx
I am passing a state from one component to another component. However, when a state updates in the parent component, the child component doesn't update. Is there something i am doing wrong in my code below .
As shown below, in the Patient.JS i pass the state to the AddPatient.JS. But when i the state of age is updated in the Patient.JS, it doesn't update in the AddPatient.JS.
How can i handle this ?
Patient.JS
state = {
info = {
age = '',
name = ''
}
}
handle_age(event)
{
this.setState({ age:event.target.value}, () => {
console.log('age', this.state.age)
<Modal>
<ModalHeader>
</ModalHeader>
<ModalBody>
<AddPatient
addNewPatient = {this.state.info}
/>
</ModalBody>
<ModalFooter>
<Button variant="contained" className="text-white" onClick={() => this.addPatient()}>Save</Button>
</ModalFooter>
</Modal>
AddPatient
const AddPatient = ({ addNewPatient}) => (
<FormGroup>
<Label for="className">Age</Label>
<Input
type="number"
name="sub_total"
id="sub_total"
placeholder=""
value = {addNewPatient.age}
/>
</FormGroup>
);
Replace your handle_age function with this:
handle_age(event)
{
let info = {...this.state.info}
info.age = event.target.value
this.setState({
info: {...info}
})
}
Explanation: When updating components, React JS does a shallow check on the state keys and values. What it means, is that it will detect a change when a key/value pair changes or updates, but won't detect a change in deeply nested objects within the state. Example:
this.state = {
visible: true,
users: {
{
id: 1,
name: 'john'
},
{
id: 2,
name: 'doe'
}
}
In the above code, React will detect a change in the visible key but not in the users object because it is a deeply nested object.
Now since you are using only the age of the patient, just pass the age as patientAge={this.state.info.age} and use it directly.
In your handler you are assigning the input value to state.age and not inside state.info.age
handle_age(event) {
const age = event.target.value;
this.setState(prevState => ({
info: { ...prevState.info, age }
}), console.log('age', this.state.info.age);
}
you are passing the state as a props to the child component so you are now entering the concept of when does change props will trigger a rerender.
if the prop is a primitive value (eg string or integer etc..) it will cause a rerender on every value change.
if the props are an array or like in your example an object, it will cause a rerender only when there's a new reference for that object or array
sense your only changing a property of an object and react dose shallow comparison of state and props, changing "age" will not trigger rerender in child.
you can do:
setState to entire info every time which will change his reference and trigger rerender at child
or
pass age and name separately as props to your child component
from what I see you are only using age in your child component so I would recommend option 2
like this:
<AddPatient
addNewPatient={this.state.info.age}
/>
const AddProduct = ({ addNewPatient}) => (
<FormGroup>
<Label for="className">Age</Label>
<Input
type="number"
name="sub_total"
id="sub_total"
placeholder=""
value = {addNewPatient}
/>
</FormGroup>
);